package com.gitee.wsl.struct.command.model.parameters.options

import com.gitee.wsl.struct.command.context.Context
import com.gitee.wsl.struct.command.model.parameters.GroupableOption
import com.gitee.wsl.struct.command.model.ParameterHolder
import com.gitee.wsl.struct.command.model.parameters.StaticallyGroupedOption
import com.gitee.wsl.struct.command.output.HelpFormatter
import com.gitee.wsl.struct.command.model.parameters.BaseOption
import com.gitee.wsl.struct.command.model.parameters.BaseOptionDelegate
import com.gitee.wsl.struct.command.parsers.OptionInvocation
import com.gitee.wsl.struct.command.context.sources.ValueSource

/**
 * An optional command line parameter that takes a fixed number of values.
 *
 * Options can take any fixed number of values, including 0.
 */
interface Option : BaseOption<List<OptionInvocation>, HelpFormatter.ParameterHelp.Option> {
    /** A name representing the values for this option that can be displayed to the user. */
    fun metavar(context: Context): String?

    /** The description of this option, usually a single line. */
    //fun help(context: Context): String

    /** The names that can be used to invoke this option. They must start with a punctuation character. */
    val names: Set<String>

    /** Names that can be used for a secondary purpose, like disabling flag options. */
    val secondaryNames: Set<String>

    /** The min and max number of values that must be given to this option. */
    val nvalues: IntRange

    /** If true, this option should not appear in help output. */
    val hidden: Boolean

    /** Extra information about this option to pass to the help formatter. */
    //val helpTags: Map<String, String>

    /** Optional set of strings to use when the user invokes shell autocomplete on a value for this option. */
    //val completionCandidates: CompletionCandidates get() = CompletionCandidates.None

    /** Optional explicit key to use when looking this option up from a [ValueSource] */
    val valueSourceKey: String?

    /** If true, this option can be specified without a name e.g. `-2` instead of `-o2` */
    val acceptsNumberValueWithoutName: Boolean get() = false

    /** If true, the presence of this option on the command line will halt parsing immediately */
    val eager: Boolean get() = false

    /** If false, invocations must be of the form `--foo=1` or `-f1`. If true, the forms `--foo 1` and `-f 1` are also accepted. */
    val acceptsUnattachedValue: Boolean get() = true

    /** Information about this option for the help output. */
    override fun parameterHelp(context: Context): HelpFormatter.ParameterHelp.Option? = when {
        hidden -> null
        else -> HelpFormatter.ParameterHelp.Option(
            names,
            secondaryNames,
            metavar(context),
            help(context),
            nvalues,
            helpTags,
            acceptsNumberValueWithoutName,
            acceptsUnattachedValue,
            groupName = (this as? StaticallyGroupedOption)?.groupName
                ?: (this as? GroupableOption)?.parameterGroup?.groupName,
        )
    }

    /**
     * Called after this command's argv is parsed to transform and store the option's value.
     *
     * You cannot refer to other parameter values during this call, since they might not have been
     * finalized yet.
     *
     * @param context The context for this parse
     * @param invocations A possibly empty list of invocations of this option.
     */
    //fun finalize(context: Context, invocations: List<OptionInvocation>)

    /**
     * Called after all of a command's parameters have been [finalize]d to perform validation of the final value.
     */
    //fun postValidate(context: Context)
}

/** An option that functions as a property delegate */
/*interface OptionDelegate<T> :
    GroupableOption,
    ReadOnlyProperty<ParameterHolder, T>,
    PropertyDelegateProvider<ParameterHolder, ReadOnlyProperty<ParameterHolder, T>> {
    *//**
     * The value for this option.
     *
     * @throws IllegalStateException if this property is accessed before [finalize] is called.
     *//*
    val value: T

    *//** Implementations must call [ParameterHolder.registerOption] *//*
    override operator fun provideDelegate(
        thisRef: ParameterHolder,
        property: KProperty<*>,
    ): ReadOnlyProperty<ParameterHolder, T>

    override fun getValue(thisRef: ParameterHolder, property: KProperty<*>): T = value
}*/

interface OptionDelegate<T> : GroupableOption,
    BaseOptionDelegate<List<OptionInvocation>, HelpFormatter.ParameterHelp.Option, ParameterHolder, T>

internal fun inferOptionNames(names: Set<String>, propertyName: String): Set<String> {
    if (names.isNotEmpty()) {
        val invalidName = names.find { !it.matches(Regex("""[\-@/+]{1,2}(?:[\w\-_]+|\?)""")) }
        require(invalidName == null) { "Invalid option name \"$invalidName\"" }
        return names
    }
    val normalizedName = "--" + propertyName.replace(Regex("""[a-z][A-Z]""")) {
        "${it.value[0]}-${it.value[1]}"
    }.lowercase()
    return setOf(normalizedName)
}

internal fun inferEnvvar(names: Set<String>, envvar: String?, autoEnvvarPrefix: String?): String? {
    if (envvar != null) return envvar
    if (names.isEmpty() || autoEnvvarPrefix == null) return null
    val name = splitOptionPrefix(names.maxBy { it.length }).second
    if (name.isEmpty()) return null
    return autoEnvvarPrefix + "_" + name.replace(Regex("\\W"), "_").uppercase()
}

/** Split an option token into a pair of prefix to simple name. */
internal fun splitOptionPrefix(name: String): Pair<String, String> =
    when {
        name.length < 2 || name[0] !in "-@/+" -> "" to name
        name.length > 2 && name[0] == name[1] -> name.slice(0..1) to name.substring(2)
        else -> name.substring(0, 1) to name.substring(1)
    }

@PublishedApi
internal fun Option.longestName(): String? = names.maxByOrNull { it.length }

